package main

import (
	"fmt"
	"sync"
)

// 1、不同goroutine之间如何通讯
//  - 全局变量加锁同步
//  - chan
// 2、chan的本质先进先出的队列
// 3、chan线程安全, 多goroutine访问时, 不需要加锁
// 4、chan是有类型的
// 5、chan的声明: var 变量名 chan 数据类型
/*
举例:
var intChan chan int
var mapChan chan map[int]string
var personChan chan Person
var personPointerChan chan *Person
*/
// 6、chan是引用类型, 必须初始化才能写入数据, 即make后才能使用
// 7、chan初始化前是nil, 初始化后是一个指针
// 8、chan的长度用len()获取, chan的容量用cap()获取
// 9、往chan里写数据: myChan<- value
// 10、从chan里读数据: value := <-myChan; 如果不用变量接收可以写成<-myChan, 相当于丢掉数据
// 11、在没有使用goroutine的情况下, 当往chan里写数据超过chan的容量或读已经空了的chan都会导致死锁
// 12、chan可以声明为只读或者只写
// 13、使用select可以解决从管道取数据的阻塞问题
// 14、goroutine在使用recover, 解决goroutine中出现panic导致程序崩溃的问题

// 声明一个全局map
var jcMap = make(map[int]int)

// 声明一个全局互斥锁
var lock sync.Mutex // Mutex: 互斥锁

// 计算1,2,3...n的阶乘
func test(n int) {
	res := 1
	for i := 1; i <= n; i++ {
		res *= i
	}
	// 加锁
	lock.Lock()
	jcMap[n] = res
	// 解锁
	lock.Unlock()
}

func UsageChan() {
	var intChan chan int // <nil>
	// 初始化一个容量为1的chan
	intChan = make(chan int, 1) // 0xc000182000
	intChan <- 1                // 往chan里写一个数据
	fmt.Println(intChan, len(intChan), cap(intChan))
	_ = <-intChan // 从chan里读一个数据
}

// 关闭chan, 关闭chan后, 不能再往chan里写数据, 但是仍然可以从chan读数据, 当全部读取完毕, 从chan里就会读出该数据类型的零值
func CloseChan() {
	var c chan int
	c = make(chan int, 3)
	c <- 10
	c <- 20
	c <- 30
	close(c)
	v1, ok := <-c
	fmt.Println(v1, ok) // 10  true
	v1, ok = <-c
	fmt.Println(v1, ok) // 20  true
	v1, ok = <-c
	fmt.Println(v1, ok) // 30  true
	v1, ok = <-c
	fmt.Println(v1, ok) // 0 false
}

// 遍历chan, 如果chan没有关闭, 遍历chan会导致死锁
func TravelChan() {
	var c chan string
	c = make(chan string, 3)
	c <- "1"
	c <- "2"
	c <- "3"
	close(c)           // 在遍历chan前, 必须关闭chan
	for v := range c { // chan的遍历不像切片、数据、字符串, 没有索引, 也不像map, 没有键, 只有一个值
		fmt.Println(v)
	}
}

func send(ch chan<- int, exit chan bool) {
	for i := 0; i < 5; i++ {
		ch <- i
	}
	close(ch)
	exit <- true
}

func recv(ch <-chan int, exit chan bool) {
	for {
		v, ok := <-ch
		if !ok {
			break
		}
		fmt.Println(v)
	}
	exit <- true
}

func main() {
	// 1、用全局互斥锁解决goroutine资源竞争
	// 开启10个goroutine去计算阶乘
	//for i := 1; i <= 10; i++ {
	//	go test(i)
	//}
	// 主线程等待10秒, 以便让10个goroutine完成
	//time.Sleep(time.Second * 10)
	//lock.Lock()
	//for k, v := range jcMap {
	//	fmt.Println(k, v)
	//}
	//lock.Unlock()

	UsageChan()
	CloseChan()
	TravelChan()

	//var writeOnly chan<- int
	//writeOnly = make(chan int, 2)
	//writeOnly<-1
	// v := <-writeOnly  错误

	//var readOnly <-chan int
	//readOnly = make(chan int)
	//<-readOnly
	// readOnly<- 1  错误

	var ch chan int
	ch = make(chan int)
	exit := make(chan bool)
	go send(ch, exit)
	go recv(ch, exit)

	var total = 0
	for _ = range exit {
		total++
		if total == 2 {
			break
		}
	}

	// select解决从管道取数据阻塞问题
	intChan := make(chan int, 10)
	for i := 0; i < 10; i++ {
		intChan <- i
	}
	stringChan := make(chan string, 5)
	for i := 0; i < 5; i++ {
		stringChan <- fmt.Sprintf("%d", i)
	}
	// 传统的方法在遍历管道时, 如果不关闭会阻塞导致死锁
	// 实际开发中, 可能不好确定什么时候关闭chan, 这时候可以用select
label:
	for {
		select {
		case v := <-intChan:
			fmt.Println("从intChan读取了数据", v)
		case v := <-stringChan:
			fmt.Println("从stringChan读取了数据", v)
		default:
			fmt.Println("都取不到")
			break label
		}
	}

	// 如果开启了一个goroutine, 但是这个goroutine出现了panic, 如果没有捕获这个panic
	// 就会造成整个程序崩溃, 这时就需要在goroutine中使用recover来捕获panic, 这样即使整个goroutine发生了问题, 主线程仍然不受影响
}
